package txx;

import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.widgets.Display;

/**
 * 
 * 简单光照模型，phong模型的实现
 * 
 */
public class Phong {

    // 画布
    private GC graphics;

    // 本次绘画的区域，由左上角顶点坐标和大小决定
    private int canvasLeft = 90;
    private int canvasTop = 90;
    private int canvasWidth = 90;
    private int canvasHeight = 90;

    // 物体对环境光的反射系数
    private double[] ka = new double[] { 0.1, 0.1, 0.1 };
    // 物体的漫反射系数，取值0～1
    private double[] kd = new double[] { 0, 0.8, 0 };
    // 物体的镜面反射系数ks
    private double[] ks = new double[] { 0, 0.2, 0 };;
    // 物体的镜面反射的反射指数，取值1～2000，越大表明物体越光滑
    private int n = 10;
    // 球的半径
    private int r = 30;
    // 视点方向(0,0,1)
    private int[] view = new int[] { 0, 0, 1 };
    // 规范化后的视点方向
    private double[] nv;
    // 光源方向 (左/右,上/下,前/后) 1代表前值,-1代表后值
    private int[] light = new int[] { -1, -1, 1 };
    // 规范化为单位向量的光源方向
    private double[] nl;
    // 入射光强
    private double[] ip = new double[] { 1, 1, 1 };
    // 环境光强
    private double[] ia = new double[] { 0.2, 0.2, 0.2 };
    // 背景颜色
    private Color gc = new Color(Display.getDefault(), 189, 238, 247);
    // 规范化为单位向量的H
    private double[] nh;

    public Phong(GC graphics, int canvasLeft, int canvasTop, double kd, double ks, int n) {
        super();
        this.graphics = graphics;
        this.canvasLeft = canvasLeft;
        this.canvasTop = canvasTop;
        this.kd = new double[] { 0, kd, 0 };
        this.ks = new double[] { 0, ks, 0 };
        this.n = n;
    }
    
    public Phong(GC graphics, int canvasLeft, int canvasTop, int canvasWidth, int canvasHeight, int r, double[] kd, double[] ks, int n, int[] light) {
        super();
        this.graphics = graphics;
        this.canvasLeft = canvasLeft;
        this.canvasTop = canvasTop;
        this.canvasWidth = canvasWidth;
        this.canvasHeight = canvasHeight;
        this.r = r;
//        this.kd = new double[] { 0, kd, 0 };
//        this.ks = new double[] { 0, ks, 0 };
        this.kd = kd;
        this.ks = ks;
        this.n = n;
        this.light = light;
    }

    /**
     * 坐标转换
     * 窗口坐标转换为三维坐标系坐标， 如果点不在球上，则返回[*,*,0,0]
     * 点在球上则返回[*,*,*,1]
     * @param xw
     * @param yw
     * @return 转换后的坐标，和是否在球上的标识
     */
    private double[] w2s(int xw, int yw) {
        // 画布高度和宽度
        double width = this.canvasWidth;
        double height = this.canvasHeight;

        // 计算二维平面的坐标
        double xs = width / 2 - xw + canvasLeft;
        double ys = height / 2 - yw + canvasTop;

        double rr = r * r;
        double xys = xs * xs + ys * ys;
        if (rr < xys) {
            return new double[] { xs, ys, 0, 0 };
        }
        double zs = sign(view[2]) * Math.sqrt(rr - xys);
        return new double[] { xs, ys, zs, 1 };
    }

    // 求单位向量
    private double[] normalize(int[] ps) {
        double t = Math.sqrt(ps[0] * ps[0] + ps[1] * ps[1] + ps[2] * ps[2]);
        if (t == 0)
            t = 1;
        return new double[] { ps[0] / t, ps[1] / t, ps[2] / t };
    }

    private double[] normalize(double[] ps) {
        double t = Math.sqrt(ps[0] * ps[0] + ps[1] * ps[1] + ps[2] * ps[2]);
        if (t == 0)
            t = 1;
        return new double[] { ps[0] / t, ps[1] / t, ps[2] / t };
    }

    // 求三点的平方和
    private double squareAdd(double x, double y, double z) {
        return x * x + y * y + z * z;
    }

    // 计算某个点的颜色
    private Color computeColor(int xw, int yw) {
        double[] ps = w2s(xw, yw);
        // 不在球上
        if (ps[3] == 0.0) {
            // 环境光
            return gc;
        }
        // 法向量N
        double[] n = normalize(ps);

        // 求光源方向L和法向量N的夹角的cos值
        // L点N
        double ln = n[0] * nl[0] + n[1] * nl[1] + n[2] * nl[2];

        // 求H点N，用来代替R点V
        double hn = n[0] * nh[0] + n[1] * nh[1] + n[2] * nh[2];

        // 求点的颜色
        double ir = ia[0] * ka[0] + ip[0] * kd[0] * ln + ip[0] * ks[0] * Math.pow(hn, this.n);
        double ig = ia[1] * ka[1] + ip[1] * kd[1] * ln + ip[1] * ks[1] * Math.pow(hn, this.n);
        double ib = ia[2] * ka[2] + ip[2] * kd[2] * ln + ip[2] * ks[2] * Math.pow(hn, this.n);
        // System.out.println("r:"+ir+" g:"+ig+" b:"+ib);
        ir = defaultValue(ir);
        ig = defaultValue(ig);
        ib = defaultValue(ib);
        // Color.FromArgb((int)(ir * 255), (int)(ig * 255), (int)(ib * 255));
        return new Color(Display.getDefault(), (int) (ir * 255), (int) (ig * 255), (int) (ib * 255));
    }

    private double defaultValue(double val) {
        if (val < 0)
            return 0;
        if (val > 1)
            return 1;
        return val;
    }

    public void drawPhong() {
        // 规范化光源方向和视点方向
        nl = normalize(this.light);
        nv = normalize(this.view);
        // 求H，为H=(L+V)/|L+V|
        double lv2 = squareAdd(nl[0] + nv[0], nl[1] + nv[1], nl[2] + nv[2]);
        double lv = sign(lv2) * Math.sqrt(Math.abs(lv2));
        if (lv == 0.0)
            lv = 1;
        nh = new double[] { (nl[0] + nv[0]) / lv, (nl[1] + nv[1]) / lv, (nl[2] + nv[2]) / lv };
        int width = this.canvasWidth;
        int height = this.canvasHeight;

        for (int yw = canvasTop + height / 2 - r, yend = canvasTop + height / 2 + r; yw < yend; yw++) {
            for (int xw = canvasLeft + width / 2 - r, xend = canvasLeft + width / 2 + r; xw < xend; xw++) {
                Color c = computeColor(xw, yw);
                graphics.setForeground(c);
                graphics.drawPoint(xw, yw);
            }
        }

    }

    private int sign(double lv2) {
        return lv2 > 0 ? 1 : -1;
    }
}
